#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<ctype.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32 
char *gArgv[NUM];
char cwd[SIZE*2];
#define SkipPath(p)do{p+=(strlen(p)-1);while(*p!='/')p--;}while(0)//这里是一个宏函数：目的是将路径的首转到最后一个字母前的/上
#define SkipSpace(cmd,pos)do{\
    while(1){\
        if(isspace(cmd[pos]))\
            pos++;\
        else break;\
    }\
   }while(0)
#define None_Redir 0
#define In_Redir 1
#define Out_Redir 2
#define App_Redir 3
int redir_type=None_Redir;
char*filename=NULL;
int Die()
{
    return -1;
}
const char* GetHome()
{
    const char*home =getenv("HOME");
    if(home==NULL)return "/";
    return home;
}
//1.字符 
const char* GetUserName()
{
    const char*name=getenv("USER");
    if(name==NULL) return "None";
    return name;
}
const char* GetHonstName()
{
    const char* name=getenv("HOSTNAME");
    if(name==NULL)return "None";
    return name;
}
const char* GETCwd()
{
   const char*cwd=getenv("PWD");
   if(cwd==NULL)return "None";
   return cwd;
}
void MakeCommandLineAndPrint()
{
    char line[SIZE];
    const char*username=GetUserName();
    const char*hostname=GetHonstName();
    const char*cwd=GETCwd();
    SkipPath(cwd);
    snprintf(line,sizeof(line),"[%s@%s %s]>",username,hostname,strlen(cwd)==1?"/":cwd+1);//将根路径排除出去
    printf("%s",line);
    
}
//2.输入命令
int GetUserCommand(char command[],size_t n)
{
    char *s=fgets(command,n,stdin);
    if(s==NULL)return -1;
    command[strlen(command)-1]=ZERO;//这里是为了解决将command的"\n"删除
    return strlen(command);
    
}

void CheckRedir(char* cmd)
{
    int pos=0;//遍历指针
    int end=strlen(cmd);
    while(pos<end)
    {
        if(cmd[pos]=='>')
        {
         if(cmd[pos+1]=='>')
         {
             cmd[pos++]=0;
             pos++;
             redir_type=App_Redir;
             SkipSpace(cmd,pos);//将符号与文件名之间的空格删除
             filename=cmd+pos;//得到文件的首地址
         }
         else
         {
             cmd[pos++]=0;
             redir_type=Out_Redir;
             SkipSpace(cmd,pos);
             filename=cmd+pos;
         }
        }
        else if(cmd[pos]=='<')
        {
            cmd[pos++]=0;
            redir_type=In_Redir;
            SkipSpace(cmd,pos);
            filename=cmd+pos;
        }
        else
        {
            pos++;
        }
    }
}
//命令分割
void SplitCommand(char command[],size_t n)
{
        (void)n;
        gArgv[0]=strtok(command,SEP);
        int index=1;
        while((gArgv[index++]=strtok(NULL,SEP)));//故意写成=,表示先赋值，在判断. 分割之后，strtok会返回NULL，刚好让gArgv最后一个元素是NULL, 并且while判断结束(最后以位是NULL的原因：交换函数的指令参数要求最后以为是NULL)
}
void Cd()
{
    const char*path=gArgv[1];
    if(path==NULL)path=GetHome();
    chdir(path);//这个是系统的函数将目录改为所给的路径
    char temp[SIZE*2];
    getcwd(temp,sizeof(temp));
    snprintf(cwd,sizeof(cwd),"PWD=%s",temp);//这样输入的原因是putenv要求输入格式是“名字=要输环境变量的内容
    putenv(cwd);
}
int CheckBuildin()
{
    int yes=0;
    const char* enter_cmd=gArgv[0];
    if(strcmp(enter_cmd,"cd")==0)
    {
        yes=1;
        Cd();
    }
    return yes;
}
void ExecuteCommand()
{
    pid_t id=fork();
    if(id<0)Die();//子进程产生失败
    else if(id==0)//子进程
    {   if(filename!=NULL)
        {
            if(redir_type==In_Redir)
            {
                int fd=open(filename,O_RDONLY);
                dup2(fd,0);
            }
            else if(redir_type==Out_Redir)
            {
                int fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
                dup2(fd,1);
            }
            else if(redir_type==App_Redir)
            {
                int fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
                dup2(fd,1);
            }
            else
            {

            }
        
        }

        execvp(gArgv[0],gArgv);
        exit(errno);
    }
    else 
    {
        int status=0;
        waitpid(id,&status,0);
    }
}
int main ()
{
    int quit=0;
    (void)quit;
    while(!quit)
   {
       //0.重置
       redir_type=None_Redir;//这里是为了重定向的种类区分
       filename=NULL;//重定向的文件
       //1.我们需要自己输出一个命令行
    MakeCommandLineAndPrint();
       //2.获取用户命令字符串(使用缓冲区来读取指令行)
    char usercommand[SIZE];
    int n=GetUserCommand(usercommand,sizeof(usercommand));
    if(n<0)return 1;
       //2.1检查重定向
    CheckRedir(usercommand);
       //3.将用户命令进行分割
    SplitCommand(usercommand,sizeof(usercommand));
       //4检测命令是否为
    n=CheckBuildin();
    if(n)continue;//是内键命令可以不用再执行，上面的函数已经执行
       //5.执行命令
    ExecuteCommand();
    }

    return 0;
}
